package im.composer.vapu;

import im.composer.audio.engine.Source;
import im.composer.ds.UnmodifiableQueue;
import im.composer.generators.RedirectedAudioIn;

import jass.engine.BufferNotAvailableException;
import jass.engine.SinkIsFullException;

import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.jaudiolibs.audioservers.AudioConfiguration;
import org.tritonus.share.sampled.FloatSampleBuffer;

public class CombinedVapu extends VAPU {

	private RedirectedAudioIn in = null;
	private final ConcurrentLinkedDeque<VAPU> queue = new ConcurrentLinkedDeque<VAPU>();
	private final ReentrantLock changeLock = new ReentrantLock();
	private final Condition notChanging = changeLock.newCondition();
	private boolean changing = false;

	public CombinedVapu() {
	}

	public CombinedVapu(AudioConfiguration context, VAPU... units) {
		super(context);
		if (units.length > 0) {
			setQueue(Arrays.asList(units));
		}
	}

	public Queue<VAPU> getQueue() {
		return new UnmodifiableQueue<VAPU>(queue);
	}

	public synchronized void setQueue(Iterable<VAPU> itr) {
		changing = true;
		queue.clear();
		addAll(itr);
		warpAudio();
		warpMidi();
		changing = false;
		try {
			notChanging.signalAll();
		} catch (Exception e) {
		}
	}

	private void addAll(Iterable<VAPU> itr) {
		for (VAPU vapu : itr) {
			if (vapu != null) {
				queue.add(vapu);
			}
		}
	}

	protected void warpAudio() {
		VAPU previous = null;
		for(VAPU u:queue){
			if(previous!=null){
				setSoleSource(u,previous);
			}
			previous = u;
		}
		if(!queue.isEmpty()){
			setSoleSource(queue.getFirst(),in);
		}
	}
	
	private void setSoleSource(VAPU u,Source src){
		while(!u.getSources().isEmpty()){
			u.removeSource(0);
		}
		try {
			u.addSource(src);
		} catch (SinkIsFullException e) {
			//should not happen;
		}
	}

	protected void warpMidi() {
		VAPU previous = null;
		for(VAPU u:queue){
			if(previous!=null){
				previous.setReceiver(u);
			}
			previous = u;
		}
	}

	@Override
	public synchronized void configure(AudioConfiguration context) throws Exception {
		in = new RedirectedAudioIn();
		in.setChannels(context.getInputChannelCount());
		in.configure(context);
		super.configure(context);
		for (VAPU u : queue) {
			u.configure(context);
		}
	}

	@Override
	protected void executeCommand(long timeStamp, String command) {

	}

	@Override
	public final void computeBuffer() {
		try {
			doComputeBuffer();
		} catch (InterruptedException e) {
		}
	}

	private void doComputeBuffer() throws InterruptedException {
		final ReentrantLock changeLock = this.changeLock;
		changeLock.lockInterruptibly();
		try {
			while (changing) {
				notChanging.await();
			}
			doComputeBuffer1();
		} finally {
			changeLock.unlock();
		}
	}

	private void doComputeBuffer1() {
		if(queue.isEmpty()){
			return;
		}
		doInput();
		doOutput();
	}

	protected void doInput() {
		FloatSampleBuffer buf = in.getBuffer();
		for(Source src: getSources()){
			try {
				buf.mix(src.getBuffer(getTime()));
			} catch (BufferNotAvailableException e) {
			}
		}
	}

	protected void doOutput() {
		VAPU tail = queue.getLast();
		tail.setTime(getTime());
		try {
			FloatSampleBuffer buf = tail.getBuffer(getTime());
			buf.copyTo(getBuffer(), 0, getBufferSize());
		} catch (BufferNotAvailableException e) {
		}
	}

}
